import asyncio
import websockets
import importlib
from datetime import datetime

from cmds.cmd import handler
from config.read import nodes_info
from dataStructs.my_databases_cmd import Pyrdb_load, AOF_write, BG_save
from redisData.flag import logo_print
from redisStrcut.redis_struct import RedisClient

import sys
import crcmod

############## hash分槽 #####################
CLUSTER_SLOTS = 16384  # Redis集群的分槽总数
crc16 = crcmod.mkCrcFun(0x11021, rev=False, initCrc=0x0000, xorOut=0x0000)  # 创建一个CRC16函数

# 计算给定键的分槽
def get_slot(key):
    crc = crc16(key.encode('utf-8'))  # 计算键的CRC16哈希值
    slot = crc % CLUSTER_SLOTS  # 将哈希值映射到一个分槽上
    return slot



############ 获取支持的所有指令 ##########
# 导入的模块列表
module_names = [
    'my_string', 'my_set', 'my_hash', 'my_list', 'my_sortedset', 'my_databases_cmd','my_pubsub'
]
all_functions = {}  # 所有的方法

# 循环遍历每个模块，并将其函数添加到总字典中
for module_name in module_names:
    module = importlib.import_module(f"dataStructs.{module_name}")
    functions = {
        name: func for name, func in module.__dict__.items()
        if callable(func)
    }
    all_functions.update(functions)

############# 创建链接的客户端字典 ###############
# 创建对客户端的映射
redis_clis = {}

############ RDB持久化 #############
# 从pyredis_databases.pyrdb中读取数据
Pyrdb_load()

############ AOF持久化 #############
# 每次服务启动记录的日志
logs = ['\n*******************************\n']


# 定期执行aof日志操作
async def periodic_task():
    while True:
        AOF_write(logs)  # aof添加
        logs.clear()  # 清除保存的log
        await asyncio.sleep(3)  # 休眠3秒执行一次


# 获取时间
def get_time():
    c_time = datetime.now()
    milliseconds = c_time.microsecond // 1000  # 获取毫秒
    return c_time.strftime("%Y-%m-%d %H:%M:%S") + f".{milliseconds:03d}"  # 格式化成字符串（精确到毫秒）


############## 事务处理简单实现 ##############
multi_result = []  # 事务结果

def multi():
    result = "\n".join(f"{i + 1}) {res}" for i, res in enumerate(multi_result))
    return result


########## 新建线程处理RDB保存 ##############
async def bgsave_handler(func_name, websocket):
    if func_name == 'bgsave':
        result_string = "Handling bgsave concurrently"


############ SOCKET服务 #################
async def handle(websocket, path):
    # 获取客户端地址
    addr = websocket.remote_address
    # 创建 RedisClient 对象，存储连接信息，默认使用第0个数据库，不开启事务
    redis_cli = RedisClient(websocket, addr[0], addr[1])
    redis_clis[addr] = redis_cli

    try:
        async for message in websocket:
            print(f"从 IP: {addr} 接收到消息: {message}")
            cmd_lists = handler(message)
            func_name = cmd_lists[0].lower()
            result = "(error) Unknown command"

            # 检查是否为退出命令
            if func_name in ('exit', 'quit'):
                print(f"IP: {addr} 断开连接！")
                break

            # 如果是删除 del 转为 del_keys
            if func_name == 'del':
                func_name = 'del_keys'
                cmd_lists[0] = 'del_keys'

            # 开启事务
            if func_name == 'multi' and len(cmd_lists) == 1 and redis_cli.redis_multi == 0:  # 当前指令为事务且只有一个指令
                redis_cli.redis_multi = 1  # 开启事务
                result = "OK"

            # 执行事务
            if func_name == 'exec' and len(cmd_lists) == 1 and redis_cli.redis_multi == 1:  # 当前指令为事务且只有一个指令
                result = multi()  # 处理事务的命令队列, 与用户对象
                multi_result.clear()  # 清除运行的结果
                redis_cli.redis_multi = 0  # 关闭事务

            # 放弃事务
            if func_name == 'discard' and len(cmd_lists) == 1 and redis_cli.redis_multi == 1:  # 当前指令为事务且只有一个指令
                multi_result.clear()  # 清除运行的结果
                redis_cli.redis_multi = 0  # 关闭事务
                result = "OK"

            # 如果开启了事务就将事务命令入队列
            if redis_cli.redis_multi:
                if func_name != 'multi':
                    logs.append(f"{get_time()} {addr}: {message}\n")  # 添加日志
                    res = "(error) Unknown command"
                    if func_name in all_functions:
                        function = all_functions[func_name]
                        res = function(cmd_lists, redis_cli) or "(error) No response"
                    multi_result.append(res)
                    result = "QUEUED"  # 返回queued
            elif func_name == 'bgsave':
                result = "Background saving started"

            # 执行其他操作
            else:
                if func_name in all_functions:
                    function = all_functions[func_name]
                    # 集群处理
                    if isCluster == 'cluster' and len(cmd_lists) > 1:
                        # todo : 判断输入输出函数,这里以简单的string去实现
                        if func_name in ['set', 'get', 'del']:
                            slot_id = get_slot(cmd_lists[1])  # 获取键的槽
                            addr_ser = reverse_mapping.get(slot_id)  # 管理此槽的addr
                            addr_port = addr_ser.split(':')[-1]  # 管理此槽的addr——port

                            if addr_port != PORT:  # 如果不是本服务地址,
                                print(f"键{cmd_lists[1]} 的槽 {slot_id} 由 {addr_ser}负责")
                                # 提示跳转
                                result = f"REDIRECT Redirected to slot [6257] located at {addr_ser}"
                            else:  # 本地服务
                                result = function(cmd_lists, redis_cli) or "(error) No response"
                                if '(error)' and '(nil)' not in result:
                                    logs.append(f"{get_time()} {addr}: {message}\n")  # 添加日志
                        else:  # 本地服务
                            result = function(cmd_lists, redis_cli) or "(error) No response"
                            if '(error)' and '(nil)' not in result:
                                logs.append(f"{get_time()} {addr}: {message}\n")  # 添加日志
                    # 单机服务
                    else:
                        result = function(cmd_lists, redis_cli) or "(error) No response"
                        if '(error)' and '(nil)' not in result:
                            logs.append(f"{get_time()} {addr}: {message}\n")  # 添加日志

            # 使用 asyncio.create_task 并发执行 bgsave_handler
            asyncio.create_task(bgsave_handler(func_name, websocket))
            # 发送响应
            await websocket.send(result)

    except websockets.exceptions.ConnectionClosed as e:
        print(f"IP: {addr} 连接异常关闭: {e}")

    finally:
        # 删除客户端信息
        del redis_clis[addr]

# client端在PING_TIMEOUT秒内没有回复server端后，server端主动断开与client端的连接
PING_TIMEOUT = 600
PONG_TIMEOUT = 600
# 在server断开与client的链接之后，在server上还保留CLOSE_TIMEOUT秒和client相关的资源，并且client可以接收来自server的消息
# 但是，client此时已不能向server发送消息
CLOSE_TIMEOUT = 10

############ 命令行执行控制 #################
# 服务开放的位置
IP = '0.0.0.0'
PORT = 1234
isCluster = 'no'
if len(sys.argv) > 1:
    PORT = sys.argv[1]  # 根据命令行的参数获取端口
    isCluster = sys.argv[2]  # 根据命令参数判断是否cluster集群模式

############### 集群模式 ###################
# 全局的槽
hash_slots = {}
reverse_mapping = {}  # 反向映射
# 如果是集群模式
if isCluster == 'cluster':

    # 读取处理配置文件
    cur_node = nodes_info()  # 读取节点配置信息
    # 处理节点配置信息
    for node in cur_node['nodes']:
        addr = node['addr']
        slots = [node['slots'][0], node['slots'][1]]
        hash_slots[addr] = slots
    # 构建反向映射
    reverse_mapping = {slot: ip_port for ip_port, slots in hash_slots.items() for slot in range(slots[0], slots[1] + 1)}

# 查找槽对应的节点
# result = reverse_mapping.get(145)

#################### 主函数 #######################
async def main():
    # 创建 WebSocket 服务器
    server = await websockets.serve(handle, IP, PORT, ping_timeout=600, close_timeout=10)
    logo_print(IP, PORT)  # 打印logo
    print(f"服务监听端口： {PORT}")
    # 创建定期执行的aof日志操作任务
    task = asyncio.create_task(periodic_task())

    try:
        # 启动事件循环，运行 WebSocket 服务器
        await server.wait_closed()
    finally:
        # 在服务器关闭后取消定期执行的任务
        task.cancel()
        await task


# 运行主程序
asyncio.run(main())
